"""
:synopsis: Creates Standard feature class from user-specified feature class.
:authors: Riley Baird (OK), Emma Baker (OK)

Implements a tool for an ArcGIS Python Toolbox.
"""


# lib import
from ...lib.session import config
from ...lib.misc import check_user_fc_for_fields, get_field_info_for_fc_creation
from ...lib.datachanges import StandardGeodatabaseCreation, StandardFeatureClassCreation
import sys
import os
import arcpy
import random
import logging
import datetime
try:
    from typing import ClassVar, Optional, Union, Protocol, TypeVar, Generic, TypeVar, overload, get_args, Literal
except:
    pass


_logger = logging.getLogger(__name__)

random_start_phrase = random.choice(['Make it so!', 'Hello World!', 'As the prophecy foretold...', 'Greetings earthlings.', 'All are bases are belong to us.', 'The Jig is up!'])

field_start_idx = 3
error_list = ["PLEASE SELECT A FEATURE CLASS TO MAP", "NO FIELDS FOUND IN SELECTED FC", "--PLEASE SELECT FIELD--"]

required_dataset_name: str = config.gdb_info.required_dataset_name # "NG911"
optional_dataset_name: str = config.gdb_info.optional_dataset_name # "OptionalLayers"
tool_switch: str = "FIELD_MAP"


class FieldMapTool(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "2 - Field Map Feature Class"
        self.description = "Field Map Fields from a user-specified Feature Class to NG911 Standards."
        self.canRunInBackground = False
        self.category = "1 - Prep"

    def getParameterInfo(self):
        """Define parameter definitions"""

        params = []

        geodatabase = arcpy.Parameter(
            displayName="NG911 Standard Geodatabase",
            name="geodatabase",
            datatype="DEWorkspace",
            parameterType="Required",
            direction="Input")
        params += [geodatabase]

        standard_fc_name = arcpy.Parameter(
            displayName="Target Standard Feature Class",
            name="standard_fc_name",
            datatype="GPString",
            parameterType="Required",
            direction="Input")

        standard_fc_name.filter.type = "ValueList"
        std_fc_name_list = [config.feature_class_info[fc_name].name for fc_name in config.feature_class_info.keys()]
        standard_fc_name.filter.list = std_fc_name_list
        params += [standard_fc_name]

        fc_path = arcpy.Parameter(
            displayName="User-specified Feature Class",
            name="fc_path",
            datatype="DEFeatureClass",
            parameterType="Required",
            direction="Input")
        params += [fc_path]

        std_fc_name_list = [config.feature_class_info[fc_name].name for fc_name in config.feature_class_info]
        for std_fc_name in std_fc_name_list:
            current_parameter_field_info_list = [config.feature_class_info[fc_name].fields for fc_name in config.feature_class_info if config.feature_class_info[fc_name].name == std_fc_name][0]
            std_fields_for_fc = [[config.field_info[config_field_name].name, error_list[0]] for config_field_name in current_parameter_field_info_list]
            disp_parameter_name = f"`{std_fc_name}` Field Mapping Table\n  [Note(s):\n    1. To evaluate standard field as Null, use value: `{error_list[2]}`]\n"
            parameter_name = std_fc_name
            field_param = arcpy.Parameter(
                displayName=disp_parameter_name,
                name=parameter_name,
                datatype="GPValueTable",
                parameterType="Optional",
                direction="Input")
            field_param.parameterDependencies = [standard_fc_name.name, fc_path.name]
            field_param.columns = [['GPString', 'Standard Field', 'ReadOnly'], ['GPString', 'User Field']]
            field_param.filters[1].type = "ValueList"
            field_param.filters[1].list = [error_list[0]]
            field_param.values = std_fields_for_fc
            params += [field_param]

        # OUTPUT
        output_fc = arcpy.Parameter(
            displayName="Output Feature Class",
            name="output_feature_class",
            datatype="DEFeatureClass",
            parameterType="Derived",
            direction="Output")
        params += [output_fc]

        return params

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""

        std_fc_name = parameters[1]
        fc = parameters[2]
        if (std_fc_name.value) and (std_fc_name.altered and not std_fc_name.hasBeenValidated):
            # Change to STD FC Name
            for para_iter in range(field_start_idx, len(parameters)):
                fc_field_map_parameter = parameters[para_iter]
                if std_fc_name.valueAsText == fc_field_map_parameter.name:
                    fc_field_map_parameter.enabled = True
                    if fc.value and fc.hasBeenValidated:
                        # fc_exists and hasBeenValidated
                        fc_path = fc.valueAsText
                        fc_filter_list, tool_value_list = check_user_fc_for_fields(fc_path, fc_field_map_parameter.values, error_list, 0, 1)
                        fc_field_map_parameter.filters[1].list = fc_filter_list
                        fc_field_map_parameter.values = tool_value_list

                    else:
                        # fc_exists doesn't exist
                        fc_path = None
                        fc_filter_list, tool_value_list = check_user_fc_for_fields(fc_path, fc_field_map_parameter.values,  error_list, 0, 1)
                        fc_field_map_parameter.filters[1].list = fc_filter_list
                        fc_field_map_parameter.values = tool_value_list
                else:
                    fc_field_map_parameter.enabled = False
        elif (std_fc_name.value and std_fc_name.hasBeenValidated) and (fc.value and fc.altered and not fc.hasBeenValidated):
            # Change to User-specified FC
            for para_iter in range(field_start_idx, len(parameters)):
                fc_field_map_parameter = parameters[para_iter]
                if std_fc_name.valueAsText == fc_field_map_parameter.name:
                    fc_field_map_parameter.enabled = True
                    fc_path = fc.valueAsText
                    fc_filter_list, tool_value_list = check_user_fc_for_fields(fc_path, fc_field_map_parameter.values, error_list, 0, 1)
                    fc_field_map_parameter.filters[1].list = fc_filter_list
                    fc_field_map_parameter.values = tool_value_list
                else:
                    fc_field_map_parameter.enabled = False
        elif std_fc_name.value and std_fc_name.hasBeenValidated:
            # Changes to gdb or fields
            for para_iter in range(field_start_idx, len(parameters)):
                fc_field_map_parameter = parameters[para_iter]
                fc_field_map_parameter.enabled = fc_field_map_parameter.enabled
                fc_field_map_parameter.values = fc_field_map_parameter.values
        else:
            # disable (lacks required info)
            for para_iter in range(field_start_idx, len(parameters)):
                fc_field_map_parameter = parameters[para_iter]
                fc_field_map_parameter.enabled = False

        return

    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""

        for parameter_index in range(field_start_idx, len(parameters)):
            current_param = parameters[parameter_index]
            if current_param.enabled == True:
                param_user_value_list = [param_val_tup[1] for param_val_tup in current_param.values]
                if error_list[0] in param_user_value_list:
                    current_param.setWarningMessage(f'{error_list[0]}')
                elif error_list[1] in param_user_value_list:
                    current_param.setErrorMessage(f'{error_list[1]}')
                elif error_list[2] in param_user_value_list:
                    unmap_fields = len([val for val in param_user_value_list if val == error_list[2]])
                    current_param.setWarningMessage(f'`{unmap_fields}` standard field(s) will be evaluated as `NULL`.')
            else:
                current_param.clearMessage()

        return

    def execute(self, parameters, messages):
        """The source code of the tool."""
        geodatabase_path = parameters[0].valueAsText
        standard_fc_name = parameters[1].valueAsText
        user_fc_path = parameters[2].valueAsText
        fc_parameter_dict = {}
        for parameter_index in range(field_start_idx, len(parameters)):  # Loop through once
            current_param = parameters[parameter_index]
            if not current_param.enabled:
                continue
            field_map_dict = {}
            for val_set in current_param.values:
                if val_set[1] in error_list:
                    field_map_dict[val_set[0]] = None
                else:
                    field_map_dict[val_set[0]] = val_set[1]
            fc_parameter_dict[current_param.name] = field_map_dict
        arcpy.AddMessage(f"{random_start_phrase}")
        if len(fc_parameter_dict.keys()) != 1:
            arcpy.AddError(f"How did this get more than one fc to field map?")
        gdb_cls = StandardGeodatabaseCreation(os.path.dirname(geodatabase_path), os.path.basename(geodatabase_path)[:-4])
        if not arcpy.Exists(gdb_cls.required_dataset_path):
            arcpy.AddWarning(f"Standard gdb `{gdb_cls.gdb_name}` does not have required dataset `{gdb_cls.required_dataset_name}`. Creating with default spatial reference (factory code: `{config.gdb_info.spatial_reference_factory_code_2d}`)...")
            gdb_sr = arcpy.SpatialReference(config.gdb_info.spatial_reference_factory_code_2d)
            gdb_cls.create_std_datasets(gdb_cls.required_dataset_name, gdb_sr)
        current_fc = config.get_feature_class_by_name(standard_fc_name)
        std_fc_geom = current_fc.geometry_type
        field_map_dict_original = fc_parameter_dict[standard_fc_name]
        field_map_dict = field_map_dict_original.copy()
        for std_field, user_field in field_map_dict_original.items():
            if user_field in error_list or user_field in ['', ' ', 'None', 'Null', None, '#']:
                del field_map_dict[std_field]
        if not field_map_dict:
            arcpy.AddError(f"Fields to map to standard not provided.")
            return
        arcpy.AddMessage(f"...Creating and Adding Standard Feature Class")
        implement_user_std_fc = StandardFeatureClassCreation(geodatabase_path, standard_fc_name)
        bad_fc_results = implement_user_std_fc.create_blank_fc(std_fc_geom)
        if bad_fc_results:
            arcpy.AddWarning(f"{bad_fc_results}")
            return
        bad_fc_results = implement_user_std_fc.user_to_std_field_mapping(user_fc_path, field_map_dict)
        if bad_fc_results:
            arcpy.AddWarning(f"{bad_fc_results}")

        parameters[-1].value = implement_user_std_fc.std_fc_path

        return


if __name__ == "__main__":
    raise Exception("This module is a dependency of an ArcGIS Python Toolbox and should not be executed directly.")
